package org.nViron.nvOrganic;

//~--- non-JDK imports --------------------------------------------------------

import java.util.Iterator;

import org.nViron.nvCommon.nvPosition;
import org.nViron.nvData.nvDataEntity;
import org.nViron.nvOrganism.nvOrganism;
import org.nViron.nvOrganism.nvStateMgr;
import org.nViron.nvParams.nvVizParamMgr.VIZPARAM;
import org.nViron.nvPlaces.nvPlace;
import org.nViron.nvRender.nvRenderer;
import org.nViron.nvRender.nvVec3;

import traer.physics.Particle;
import traer.physics.ParticleSystem;
import traer.physics.Vector3D;

/**
 * A organic is an information plant that grows on a specific place and contains all the data that is associated to this place
 * @author Roland Haring <p>
 * <script type="text/javascript" src="http://www.ohloh.net/p/376434/widgets/project_basic_stats.js"></script>
 */
public class nvOrganic extends nvOrganism {
    private final ParticleSystem particleSystem = new ParticleSystem();
    private nvVec3 cameraPos = new nvVec3();
    private float scale = 1.0f;
    private nvVec3 userPos = new nvVec3();
    private nvVec3 renderPos = new nvVec3();
    private nvVec3 cursorPos = new nvVec3();
    private nvPlace place;
    private nvPosition position;
    private nvRoot root;

    public nvOrganic(nvOrganicsMgr organicMgr, nvPlace place) {
        super(organicMgr);
        this.position = new nvPosition(place.getCenter());
        this.place    = place;
        particleSystem.setGravity(0.0f, 0.0f, 0.0f);

        // particleSystem.setIntegrator(ParticleSystem.MODIFIED_EULER);
        root = new nvRoot(this);
    }

    public void addEntity(nvDataEntity entity) {
        root.addEntity(entity);
    }

    @Override
    public void degenerate() {
        root.degenerate();
    }

    public Particle generateParticle(nvLeaf _leaf) {

        // TODO setup particle
        Vector3D parentPos = _leaf.getConnectingJoint().getSource().getParticle().position();
        Particle particle  = particleSystem.makeParticle(1.0f, parentPos.x(), parentPos.y(), parentPos.z(), _leaf);

        particle.velocity().set(_leaf.getInitialVelocity());

        // setup springs and attractions
        if (true) {
            if (_leaf.getConnectingJoint().getSource().getNumChildren() > 1) {

                // repulsion to parent
                nvNode parentNode = (nvNode) _leaf.getConnectingJoint().getSource();

                particleSystem.makeAttraction(particle, parentNode.getParticle(), -0.0001f, _leaf.getC1() / 2.0f,
                                              _leaf.getC1());

                // repulsion to sibling leafs
                for (Iterator it = parentNode.getStalks().iterator(); it.hasNext(); ) {
                    nvStalk stalk = (nvStalk) it.next();

                    if ((stalk.getLeaf() == _leaf) || (stalk.getState() != nvStateMgr.LIVING)) {
                        continue;
                    }

                    if (stalk.getLeaf().hasParticle()) {
                        particleSystem.makeAttraction(particle, stalk.getLeaf().getParticle(), -0.0001f,
                                                      _leaf.getC2() / 2.0f, _leaf.getC2());
                    }
                }

                // repulsion to sibling nodes
                for (Iterator it = parentNode.getStems().iterator(); it.hasNext(); ) {
                    nvStem stem = (nvStem) it.next();

                    if (stem.getState() != nvStateMgr.LIVING) {
                        continue;
                    }

                    if (stem.getNode().hasParticle()) {
                        particleSystem.makeAttraction(particle, stem.getNode().getParticle(), -0.0001f,
                                                      _leaf.getC2() / 2.0f, _leaf.getC2());
                    }
                }
            }
        }

        return particle;
    }

    public Particle generateParticle(nvNode _node) {

        // setup node particle
        Vector3D parentPos = _node.getConnectingJoint().getSource().getParticle().position();
        Particle particle  = particleSystem.makeParticle(1.0f, parentPos.x(), parentPos.y(), parentPos.z(), _node);

        // System.out.println("MAKE " + particle.position().toString());
        particle.velocity().set(_node.getInitialVelocity());

        // setup springs and attractions
        if (_node.getConnectingJoint().getSource().getNumChildren() > 1) {

            // repulsion to parent
            nvNode parentNode = (nvNode) _node.getConnectingJoint().getSource();

            particleSystem.makeAttraction(particle, parentNode.getParticle(), -0.0001f, _node.getC1() / 2.0f,
                                          _node.getC1());

            // repulsion to siblings
            for (Iterator it = parentNode.getStems().iterator(); it.hasNext(); ) {
                nvStem stem = (nvStem) it.next();

                if ((stem.getNode() == _node) || (stem.getState() != nvStateMgr.LIVING)) {
                    continue;
                }

                if (stem.getNode().hasParticle()) {
                    particleSystem.makeAttraction(particle, stem.getNode().getParticle(), -0.0001f,
                                                  _node.getC2() / 2.0f, _node.getC2());
                }
            }
        }

        return particle;
    }

    public Particle generateParticle(nvRoot _root) {

        // setup root particle
        Particle particle = particleSystem.makeParticle(1.0f, 0, 0, 0, _root);

        particle.velocity().set(_root.getInitialVelocity());
        particle.makeFixed();

        return particle;
    }

    public nvVec3 getCameraPos() {
        return cameraPos;
    }

    public nvVec3 getCurrentRenderPos() {
        return new nvVec3(renderPos);
    }

    public nvVec3 getCursorPos() {
        return cursorPos;
    }

    public nvOrganicsMgr getOrganicsMgr() {
        return (nvOrganicsMgr) getOrganismMgr();
    }

    public ParticleSystem getParticleSystem() {
        return particleSystem;
    }

    public nvPlace getPlace() {
        return place;
    }

    public nvPosition getPosition() {
        return position;
    }

    public nvRoot getRoot() {
        return root;
    }

    public float getScale() {
        return this.scale;
    }

    public nvVec3 getUserPos() {
        return userPos;
    }

    @Override
    public void remove() {
        root.remove();
        super.remove();
    }

    public boolean removeEntity(nvDataEntity entity) {
        boolean bRet = root.removeEntity(entity);

        return bRet;
    }

    public void removeParticle(Particle particle) {
        particleSystem.removeParticle(particle);
    }

    public void render(nvRenderer renderer) {
        renderer.renderOrganic(this);
        root.render(renderer);
        renderer.postRenderOrganic(this);
    }

    public void setPlace(nvPlace place) {
        this.place = place;
    }

    public void setPosition(nvPosition position) {
        this.position = position;
    }

    public void setScale(float scale) {
        this.scale = scale;
    }

    public void sprout() {
        root.setState(nvStateMgr.SPROUTING);
    }

    @Override
    public void update(float timeStep) {
        updatePositions();
        root.update(timeStep);
        setState(root.getState());

        if (getState() == nvStateMgr.DEAD) {
            particleSystem.clear();
        } else {
            particleSystem.tick(timeStep);
        }
    }

    private void updatePositions() {

        // positions need to be recalculation before each update because coordinate system offset can change!
        renderPos = getOrganismMgr().getSimulator().getProjection().geoToViz(position);
        cursorPos = getOrganicsMgr().getSimulator().getProjection().geoToViz(
            getOrganicsMgr().getSimulator().getCursorPosition());
        cursorPos.sub(renderPos);
        userPos =
            getOrganicsMgr().getSimulator().getProjection().geoToViz(getOrganicsMgr().getSimulator().getUserPosition());
        userPos.sub(renderPos);
        cameraPos = getOrganicsMgr().getSimulator().getProjection().geoToViz(
            getOrganicsMgr().getSimulator().getCameraPosition());
        cameraPos.sub(renderPos);
    }

    public void updateVizValue(VIZPARAM param) {
        root.updateVizValue(param);
    }
}



